#ifndef MULTITYPECELLTABLE_H_JM_20121129
#define MULTITYPECELLTABLE_H_JM_20121129

// ## c++
#include <memory>
#include <vector>
#include <algorithm>
#include <sstream>
// ## jmlib
#include "../../NoType.h"
#include "../../cast.h"

namespace jmlib {

	template<typename T1 = NoType, typename T2 = NoType, typename T3 = NoType>
	class MultiTypeCellTable {
	public:	

		typedef T1 ValueCellType1;
		typedef T2 ValueCellType2;
		typedef T3 ValueCellType3;

		template<typename T> class ValueCell;

		template<typename T>
		struct CellVisitorCreator {
			virtual void visit(ValueCell<T>* ) = 0;
		};

		template<>
		struct CellVisitorCreator<NoType> {};

		struct CellVisitor 
			: public CellVisitorCreator<ValueCellType1>
			, public CellVisitorCreator<ValueCellType2>
			, public CellVisitorCreator<ValueCellType3>
		{ };

		class Cell {
		public:
			virtual void acceptVisitor(CellVisitor* visitor) = 0;
			virtual std::string toString() const = 0;
		};

		template<typename T>
		class ValueCell : public Cell {
		public:
			typedef T valueType;

			ValueCell() { }
			ValueCell(T value) : cellValue_(value) { }

			valueType getValue() const { return cellValue_; }
			void setValue(const valueType& val) { cellValue_ = val; }

			virtual std::string toString() const override {
				return jmlib::convert<std::string>(cellValue_);
			}

			virtual void acceptVisitor(CellVisitor* visitor) override {
				acceptVisitorCreator((CellVisitorCreator<valueType>*)visitor);
			}

		private:
			valueType cellValue_;

			template<class ExactVisitor>
			void acceptVisitorCreator(ExactVisitor* visitor) {
				visitor->visit(this);
			}
			void acceptVisitorCreator(CellVisitorCreator<NoType>* visitor) { }
		};

		class Column {
		public:		
			typedef std::shared_ptr<Cell> CellPtr;

			typedef std::vector<CellPtr> InnerColumn;

			size_t getCellCount() const { return column_.size(); }

			CellPtr getCell(size_t i) { return column_[i]; }			
			const CellPtr getCell(size_t i) const { return column_[i]; }

			Column& setCell(size_t i, CellPtr v) { 
				column_[i] = v; 
				return *this;
			}

			Column& addCell(CellPtr v) { 
				column_.push_back(v); 
				return *this;
			}

			template<typename T>
			Column& setValueCell(size_t i, T v) { 
				column_[i] = CellPtr(new ValueCell<T>(v)); 
				return *this;
			}

			template<typename T>
			Column& addValueCell(T v) { 
				column_.push_back(CellPtr(new ValueCell<T>(v))); 
				return *this;
			}

			void clear() { column_.clear(); }
			bool isEmpty() const { return column_.empty(); }

			void acceptVisitor(CellVisitor* visitor) {
				std::for_each(column_.begin(), column_.end(), 
					[&visitor] (CellPtr cell) {
						cell->acceptVisitor(visitor);
				});
			}
		private:
			InnerColumn column_;
		};

		typedef std::shared_ptr<Column> ColumnPtr;
		/// Typ celej tabulky dat.
		typedef std::vector<ColumnPtr> InnerTable;

		ColumnPtr addColumn() { 
			ColumnPtr c(new Column);
			table_.push_back(c);
			return c;
		}

		void clear() {
			table_.clear();
		}

		void addColumns(size_t count) {
			for (size_t i = 0; i < count; ++i) { 
				table_.push_back(ColumnPtr(new Column));
			}
		}

		size_t getRowCount() const {
			if (isEmpty()) {
				return 0;
			}
			else {
				return getColumn(0)->getCellCount();
			}
		}

		size_t getColumnCount() const { return table_.size(); }
		ColumnPtr getColumn(size_t index) { return table_[index]; }
		const ColumnPtr getColumn(size_t index) const { return table_[index]; }
		bool isEmpty() const { return table_.empty(); }

		void acceptVisitor(CellVisitor* visitor) {
			std::for_each(table_.begin(), table_.end(), 
				[&visitor] (ColumnPtr column) {
					column->acceptVisitor(visitor);
			});
		}

		std::string getLine(const size_t row, 
			const char* lineEnd = "\n", const char* sep = ",") {
				std::stringstream stream;
				const char* separator = "";
				for (size_t i = 0; i < getColumnCount(); ++i) {
					// Pri prvom volani je separator "" az potom sa nastavi na 
					// realny separator.
					stream << separator << getColumn(i)->getCell(row)->toString();
					separator = sep;
				}
				stream << lineEnd;
				return stream.str();
		}

	private:
		InnerTable table_;
	};

} // namespace jmlib

#endif
